use std::mem;

use crate::GBTerm;

/// ## Registers
///
/// 16-bit |Hi |Lo | Name/Function
/// -------|---|---|--------------
///    AF  | A | - | Accumulator & Flags
///    BC  | B | C | BC
///    DE  | D | E | DE
///    HL  | H | L | HL
///    SP  | - | - | Stack Pointer
///    PC  | - | - | Program Counter/Pointer
/// #### [see pandoc](https://gbdev.io/pandocs/CPU_Registers_and_Flags.html)
#[repr(C)]
#[derive(Debug)]
pub struct Registers {
    /// Accumulator & Flags
    af: u16,
    bc: u16,
    de: u16,
    hl: u16,
    /// Stack Pointer
    sp: u16,
    /// Program Counter/Pointer
    pc: u16,
}

/// Register
#[derive(Debug, Copy, Clone)]
pub enum Register {
    AF,
    A,
    F,
    BC,
    B,
    C,
    DE,
    D,
    E,
    HL,
    H,
    L,
    SP,
    PC,
}

impl Registers {
    pub fn new(gb_term: GBTerm) -> Self {
        let mut registers = unsafe { mem::transmute::<[u16; 6], Registers>([0u16; 6]) };
        match gb_term {
            GBTerm::GB => {
                registers.set_u8(Register::A, 0x01);
            }
            GBTerm::GBP => {
                registers.set_u8(Register::A, 0xFF);
            }
            GBTerm::GBC => {
                registers.set_u8(Register::A, 0x11);
            }
            GBTerm::SGB => {
                registers.set_u8(Register::A, 0x01);
            }
        }
        registers.set_u8(Register::F, 0xB0);
        registers.set_u8(Register::B, 0x00);
        registers.set_u8(Register::C, 0x13);
        registers.set_u8(Register::D, 0x00);
        registers.set_u8(Register::E, 0xD8);
        registers.set_u8(Register::H, 0x01);
        registers.set_u8(Register::L, 0x4D);
        // The GameBoy stack pointer is initialized to 0xfffe on power up but a programmer should not rely on this
        // setting and rather should explicitly set its value.
        registers.set_u16(Register::SP, 0xFFFE);
        // On power up, the GameBoy Program Counter is initialized to 0x0100 and the instruction found at this location
        // in ROM is executed. The Program Counter from this point on is controlled, indirectly, by the program
        // instructions themselves that were generated by the programmer of the ROM cart.
        registers.set_u16(Register::PC, 0x0100);
        registers
    }

    pub fn get_u16(&self, reg: Register) -> u16 {
        match reg {
            Register::AF => self.af,
            Register::BC => self.bc,
            Register::DE => self.de,
            Register::HL => self.hl,
            Register::SP => self.sp,
            Register::PC => self.pc,
            other => panic!("Unsupported operation: {} for register:{:?}", "get_u16", other),
        }
    }

    pub fn set_u16(&mut self, reg: Register, val: u16) {
        match reg {
            Register::AF => self.af = val,
            Register::BC => self.bc = val,
            Register::DE => self.de = val,
            Register::HL => self.hl = val,
            Register::SP => self.sp = val,
            Register::PC => self.pc = val,
            other => panic!("Unsupported operation: {} for register:{:?} with val:{}", "set_u16", other, val),
        };
    }

    pub fn get_and_incr_by_u16(&mut self, reg: Register, by: u16) -> u16 {
        match reg {
            Register::AF => {
                let v = self.af;
                self.af = v.wrapping_add(by);
                v
            }
            Register::BC => {
                let v = self.bc;
                self.bc = v.wrapping_add(by);
                v
            }
            Register::DE => {
                let v = self.de;
                self.de = v.wrapping_add(by);
                v
            }
            Register::HL => {
                let v = self.hl;
                self.hl = v.wrapping_add(by);
                v
            }
            Register::SP => {
                let v = self.sp;
                self.sp = v.wrapping_add(by);
                v
            }
            Register::PC => {
                let v = self.pc;
                self.pc = v.wrapping_add(by);
                v
            }
            other => panic!("Unsupported operation: {} for register:{:?}", "get_and_incr_u16", other),
        }
    }

    pub fn get_and_incr_u16(&mut self, reg: Register) -> u16 {
        self.get_and_incr_by_u16(reg, 1)
    }

    pub fn incr_by_and_get_u16(&mut self, reg: Register, by: u16) -> u16 {
        match reg {
            Register::AF => {
                self.af = self.af.wrapping_add(by);
                self.af
            }
            Register::BC => {
                self.bc = self.bc.wrapping_add(by);
                self.bc
            }
            Register::DE => {
                self.de = self.de.wrapping_add(by);
                self.de
            }
            Register::HL => {
                self.hl = self.hl.wrapping_add(by);
                self.hl
            }
            Register::SP => {
                self.sp = self.sp.wrapping_add(by);
                self.sp
            }
            Register::PC => {
                self.pc = self.pc.wrapping_add(by);
                self.pc
            }
            other => panic!("Unsupported operation: {} for register:{:?}", "incr_and_get_u16", other),
        }
    }

    pub fn incr_and_get_u16(&mut self, reg: Register) -> u16 {
        self.incr_by_and_get_u16(reg, 1)
    }

    pub fn get_and_decr_u16(&mut self, reg: Register) -> u16 {
        match reg {
            Register::AF => {
                let v = self.af;
                self.af = v.wrapping_sub(1);
                v
            }
            Register::BC => {
                let v = self.bc;
                self.bc = v.wrapping_sub(1);
                v
            }
            Register::DE => {
                let v = self.de;
                self.de = v.wrapping_sub(1);
                v
            }
            Register::HL => {
                let v = self.hl;
                self.hl = v.wrapping_sub(1);
                v
            }
            Register::SP => {
                let v = self.sp;
                self.sp = v.wrapping_sub(1);
                v
            }
            Register::PC => {
                let v = self.pc;
                self.pc = v.wrapping_sub(1);
                v
            }
            other => panic!("Unsupported operation: {} for register:{:?}", "get_and_decr_u16", other),
        }
    }

    pub fn decr_by_and_get_u16(&mut self, reg: Register, by: u16) -> u16 {
        match reg {
            Register::AF => {
                self.af = self.af.wrapping_sub(by);
                self.af
            }
            Register::BC => {
                self.bc = self.bc.wrapping_sub(by);
                self.bc
            }
            Register::DE => {
                self.de = self.de.wrapping_sub(by);
                self.de
            }
            Register::HL => {
                self.hl = self.hl.wrapping_sub(by);
                self.hl
            }
            Register::SP => {
                self.sp = self.sp.wrapping_sub(by);
                self.sp
            }
            Register::PC => {
                self.pc = self.pc.wrapping_sub(by);
                self.pc
            }
            other => panic!("Unsupported operation: {} for register:{:?}", "decr_and_get_u16", other),
        }
    }

    pub fn decr_and_get_u16(&mut self, reg: Register) -> u16 {
        self.decr_by_and_get_u16(reg, 1)
    }

    pub fn get_u8(&self, reg: Register) -> u8 {
        match reg {
            Register::A => (self.af >> 8) as u8,
            Register::F => self.af as u8,
            Register::B => (self.bc >> 8) as u8,
            Register::C => self.bc as u8,
            Register::D => (self.de >> 8) as u8,
            Register::E => self.de as u8,
            Register::H => (self.hl >> 8) as u8,
            Register::L => self.hl as u8,
            other => panic!("Unsupported operation: {} for register:{:?}", "get_u8", other),
        }
    }

    pub fn set_u8(&mut self, reg: Register, val: u8) {
        match reg {
            Register::A => self.af = (self.af & 0x00FF) | ((val as u16) << 8),
            Register::F => self.af = (self.af & 0xFF00) | (val as u16),
            Register::B => self.bc = (self.bc & 0x00FF) | ((val as u16) << 8),
            Register::C => self.bc = (self.bc & 0xFF00) | (val as u16),
            Register::D => self.de = (self.de & 0x00FF) | ((val as u16) << 8),
            Register::E => self.de = (self.de & 0xFF00) | (val as u16),
            Register::H => self.hl = (self.hl & 0x00FF) | ((val as u16) << 8),
            Register::L => self.hl = (self.hl & 0xFF00) | (val as u16),
            other => panic!("Unsupported operation: {} for register:{:?}", "set_u8", other),
        };
    }
}

///
/// ## The Flags Register (lower 8 bits of AF register)
///
/// Bit | Name | Explanation
/// ----|------|-------
///   7 |   z  | Zero flag
///   6 |   n  | Subtraction flag (BCD)
///   5 |   h  | Half Carry flag (BCD)
///   4 |   c  | Carry flag
#[derive(Copy, Clone)]
pub enum Flag {
    /// Bit | Name | Explanation
    /// ----|------|-------
    ///   7 |   z  | Zero flag
    Z = 0x0080,
    /// Bit | Name | Explanation
    /// ----|------|-------
    ///   6 |   n  | Subtraction flag (BCD)
    N = 0x0040,
    /// Bit | Name | Explanation
    /// ----|------|-------
    ///   5 |   h  | Half Carry flag (BCD)
    H = 0x0020,
    /// Bit | Name | Explanation
    /// ----|------|-------
    ///   4 |   c  | Carry flag
    C = 0x0010,
}

/// Flag Registers
impl Registers {
    pub fn get_flag(&self, flag: Flag) -> bool {
        self.af & (flag as u16) > 0
    }

    #[inline]
    pub fn set_flag(&mut self, flag: Flag, val: bool) {
        if val {
            self.af = self.af | (flag as u16);
        } else {
            self.af = self.af & (!(flag as u16));
        }
    }
}


#[cfg(test)]
mod tests {
    use crate::cpu::lr35902::registers::{Register, Registers};
    use crate::GBTerm;

    #[test]
    pub fn test_bc() {
        let mut registers: Registers = Registers::new(GBTerm::GB);
        registers.set_u16(Register::BC, 0x0001);
        let bc = registers.get_u16(Register::BC);
        assert_eq!(bc, 0x0001);
        registers.set_u16(Register::BC, 0x0002);
        let bc = registers.get_u16(Register::BC);
        assert_eq!(bc, 0x0002);
    }

    #[test]
    pub fn test_bc_lo() {
        let mut registers: Registers = Registers::new(GBTerm::GB);
        registers.set_u16(Register::BC, 0x1001);
        registers.set_u8(Register::C, 0x02);
        let bc = registers.get_u16(Register::BC);
        assert_eq!(bc, 0x1002);
        let bc_lo = registers.get_u8(Register::C);
        assert_eq!(bc_lo, 0x02);
    }

    #[test]
    pub fn test_bc_hi() {
        let mut registers: Registers = Registers::new(GBTerm::GB);
        registers.set_u16(Register::BC, 0x1001);
        registers.set_u8(Register::B, 0x20);
        let bc = registers.get_u16(Register::BC);
        assert_eq!(bc, 0x2001);
        let bc_lo = registers.get_u8(Register::B);
        assert_eq!(bc_lo, 0x20);
    }
}